﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Web;
using System.Web.Routing;
using Nop.Core;
using Nop.Core.Domain.Directory;
using Nop.Core.Domain.Localization;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Payments;
using Nop.Core.Plugins;
using Nop.Plugin.Payments.LibraPay.Controllers;
using Nop.Plugin.Payments.LibraPay.Models;
using Nop.Services.Configuration;
using Nop.Services.Directory;
using Nop.Services.Localization;
using Nop.Services.Orders;
using Nop.Services.Payments;
using Nop.Services.Tax;
using Nop.Web.Framework;
using Nop.Services.Logging;
using System.Xml;
using System.IO;

namespace Nop.Plugin.Payments.LibraPay
{
	/// <summary>
	///		<para>Processes payments through LibraPay, Romanian-based credit card proxy.</para>
	///		<para>Sends calls to payment gateway API and receives the responses from the gateway, taking proper actions.</para>
	/// </summary>
	public class LibraPayPaymentProcessor : BasePlugin, IPaymentMethod
	{
		#region Fields

		private readonly LibraPayPaymentSettings _LibraPayPaymentSettings;
		private readonly ISettingService _settingService;
		private readonly ICurrencyService _currencyService;
		private readonly CurrencySettings _currencySettings;
		private readonly IWebHelper _webHelper;
		private readonly ICheckoutAttributeParser _checkoutAttributeParser;
		private readonly ITaxService _taxService;
		private readonly HttpContextBase _httpContext;
		private readonly ILocalizationService _localizationService;
		private readonly ILanguageService _languageService;
		private readonly ILogger _logger;

		#endregion

		#region Constructor

		public LibraPayPaymentProcessor(LibraPayPaymentSettings LibraPayPaymentSettings, ISettingService settingService,
			ICurrencyService currencyService, CurrencySettings currencySettings, IWebHelper webHelper,
			ICheckoutAttributeParser checkoutAttributeParser, ITaxService taxService, HttpContextBase httpContext,
			ILocalizationService localizationService, ILanguageService languageService, ILogger loggingService)
		{
			this._LibraPayPaymentSettings = LibraPayPaymentSettings;
			this._settingService = settingService;
			this._currencyService = currencyService;
			this._currencySettings = currencySettings;
			this._webHelper = webHelper;
			this._checkoutAttributeParser = checkoutAttributeParser;
			this._taxService = taxService;
			this._httpContext = httpContext;
			this._localizationService = localizationService;
			this._languageService = languageService;
			this._logger = loggingService;
		}

		#endregion

		#region Utilities

		/// <summary>
		///		<para>Saves a localizable resource (string) in the database repository.</para>
		/// </summary>
		/// <param name="language"></param>
		/// <param name="resources"></param>
		private void InstallResources(Language language, IEnumerable<KeyValuePair<string, string>> resources)
		{
			LocaleStringResource nopResource;
			foreach (KeyValuePair<string, string> resource in resources)
			{
				nopResource = this._localizationService.GetLocaleStringResourceByName(resource.Key, language.Id, false);
				if (nopResource == null)
				{
					nopResource = new LocaleStringResource();
					nopResource.Language = language;
					nopResource.ResourceName = resource.Key;
					nopResource.ResourceValue = resource.Value;
					this._localizationService.InsertLocaleStringResource(nopResource);
				}
				else
				{
					nopResource.ResourceValue = resource.Value;
					this._localizationService.UpdateLocaleStringResource(nopResource);
				}
				nopResource = null;
			}
		}

		#endregion

		#region Methods

		/// <summary>
		///		<para>Process a payment. Since all data capture and payment is done by LibraPay payments site/gateway,</para>
		///		<para>this method only sets a Pending status to the payment being initiated.</para>
		///		<para>Placing an order by customer does not require previous operations;</para>
		///		<para>order details are POSTed to LibraPay payments site/gateway. See PostProcessPayment method.</para>
		/// </summary>
		/// <param name="processPaymentRequest">Payment info required for an order processing</param>
		/// <returns>Process payment result</returns>
		public ProcessPaymentResult ProcessPayment(ProcessPaymentRequest processPaymentRequest)
		{
			var result = new ProcessPaymentResult();
			result.NewPaymentStatus = PaymentStatus.Pending;
			return result;
		}

		/// <summary>
		///		<para>Post process payment (used by payment gateways that require redirecting to a third-party URL).</para>
		/// 	<para>This method POSTs order details to LibraPay payments site/gateway.</para>
		/// 	<para>Displays a redirection notice to shopper before sending to LibraPay processing site</para>
		/// </summary>
		/// <param name="postProcessPaymentRequest">Payment info required for an order processing</param>
		public void PostProcessPayment(PostProcessPaymentRequest postProcessPaymentRequest)
		{
			postProcessPaymentRequest.Order.PaymentStatus = PaymentStatus.Pending;
			if (this._LibraPayPaymentSettings.NotifyBeforeRedirect)
			{
				_httpContext.Response.Redirect(_webHelper.GetStoreLocation() + "Plugins/PaymentLibraPay/AuthorizeRequest?OrderID=" + postProcessPaymentRequest.Order.Id.ToString());
			}
			else
			{
				string ShippingTaxItemName = this._localizationService.GetResource("Plugins.Payments.LibraPay.Shopper.ShippingTaxItemName");
				string OrderDiscountItemName = this._localizationService.GetResource("Plugins.Payments.LibraPay.Shopper.OrderDiscountItemName");
				Order yeOrder = postProcessPaymentRequest.Order;
				AuthorizeModel preAuthFields = new AuthorizeModel();
				preAuthFields.AMOUNT = yeOrder.OrderTotal.ToString("#0.00", CultureInfo.GetCultureInfo("en-us"));
				preAuthFields.CURRENCY = yeOrder.CustomerCurrencyCode.ToUpper();
				preAuthFields.ORDER = (yeOrder.Id + 1000000000).ToString();
				preAuthFields.DESC = this._localizationService.GetResource("Plugins.Payments.LibraPay.Shopper.OrderDescriptionFormat").Replace("{OrderID}", yeOrder.Id.ToString()).Replace("{MerchantName}", _LibraPayPaymentSettings.MerchantName.ToUpper());
				preAuthFields.DESC = LibraPayGatewayMessage.ChopString(preAuthFields.DESC, 50);
				preAuthFields.MERCH_NAME = LibraPayGatewayMessage.ChopString(_LibraPayPaymentSettings.MerchantName, 50);
				preAuthFields.MERCH_URL = LibraPayGatewayMessage.ChopString(_LibraPayPaymentSettings.MerchantURL, 50);
				preAuthFields.MERCHANT = _LibraPayPaymentSettings.TerminalId.PadLeft(15, '0');
				preAuthFields.TERMINAL = _LibraPayPaymentSettings.TerminalId;
				preAuthFields.EMAIL = LibraPayGatewayMessage.ChopString(_LibraPayPaymentSettings.MerchantEmail, 50);
				preAuthFields.TRTYPE = "0";
				preAuthFields.COUNTRY = null;
				preAuthFields.MERCH_GMT = null;
				preAuthFields.TIMESTAMP = DateTime.Now.ToUniversalTime().ToString("yyyyMMddhhmmss");
				preAuthFields.NONCE = Guid.NewGuid().ToString().Replace("-", string.Empty);
				preAuthFields.BACKREF = _webHelper.GetStoreLocation() + "Plugins/PaymentLibraPay/Authorize";
				preAuthFields.DATA_CUSTOM = preAuthFields.ComputeCustomData_AuthorizeRequest(yeOrder, ShippingTaxItemName, OrderDiscountItemName);
				preAuthFields.P_SIGN = preAuthFields.ComputePSign_AuthorizeRequest(_LibraPayPaymentSettings.EncryptionKey);
				
				RemotePost poster = new RemotePost();
				poster.Url = _LibraPayPaymentSettings.UseSandbox ? LibraPayPaymentSettings.PaymentGatewayURL_Test : LibraPayPaymentSettings.PaymentGatewayURL_Live;
				poster.Method = "post";
				poster.FormName = "PaymentForm";
				poster.Add("AMOUNT", preAuthFields.AMOUNT);
				poster.Add("CURRENCY", preAuthFields.CURRENCY);
				poster.Add("ORDER", preAuthFields.ORDER);
				poster.Add("DESC", preAuthFields.DESC);
				poster.Add("TERMINAL", preAuthFields.TERMINAL);
				poster.Add("TIMESTAMP", preAuthFields.TIMESTAMP);
				poster.Add("NONCE", preAuthFields.NONCE);
				poster.Add("BACKREF", preAuthFields.BACKREF);
				poster.Add("DATA_CUSTOM", preAuthFields.DATA_CUSTOM);
				poster.Add("P_SIGN", preAuthFields.P_SIGN);
				poster.Post();
			}
		}

		/// <summary>
		/// 	<para>Gets additional handling fee</para>
		/// </summary>
		/// <returns>Additional handling fee</returns>
		public decimal GetAdditionalHandlingFee(IList<ShoppingCartItem> cart)
		{
			return _LibraPayPaymentSettings.AdditionalFee;
		}

		/// <summary>
		/// 	<para>Captures payment: effectively charge the money from credit card after having them blocked through authorization.</para>
		/// </summary>
		/// <param name="capturePaymentRequest">Payment info required for an order processing</param>
		/// <returns>A list of errors (of type CapturePaymentRequest)</returns>
		public CapturePaymentResult Capture(CapturePaymentRequest capturePaymentRequest)
		{
			CapturePaymentResult result = new CapturePaymentResult();
			result.AddError("Capture method not supported");
			result.NewPaymentStatus = PaymentStatus.Paid;
			return result;
		}

		/// <summary>
		/// 	<para>Refunds a payment.</para>
		/// </summary>
		/// <param name="refundPaymentRequest">Payment info required for an order processing</param>
		/// <returns>A list of errors (of type RefundPaymentRequest)</returns>
		public RefundPaymentResult Refund(RefundPaymentRequest refundPaymentRequest)
		{
			RefundPaymentResult result = new RefundPaymentResult();
			result.AddError("Refund method not supported");
			result.NewPaymentStatus = PaymentStatus.Refunded;
			return result;
		}

		/// <summary>
		/// 	<para>Voids a payment (not supported).</para>
		/// </summary>
		/// <param name="voidPaymentRequest">Payment info required for an order processing</param>
		/// <returns>A list of errors (of type VoidPaymentResult)</returns>
		public VoidPaymentResult Void(VoidPaymentRequest voidPaymentRequest)
		{
			var result = new VoidPaymentResult();
			result.AddError("Void method not supported");
			result.NewPaymentStatus = PaymentStatus.Voided;
			return result;
		}

		/// <summary>
		/// 	<para>Processes a recurring payment (not supported).</para>
		/// </summary>
		/// <param name="processPaymentRequest">Payment info required for an order processing</param>
		/// <returns>A list of errors (of type ProcessPaymentResult)</returns>
		public ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentRequest processPaymentRequest)
		{
			var result = new ProcessPaymentResult();
			result.AddError("Recurring payments not supported");
			return result;
		}

		/// <summary>
		/// 	<para>Cancels a recurring payment (not supported).</para>
		/// </summary>
		/// <param name="cancelPaymentRequest">Payment info required for an order processing</param>
		/// <returns>A list of errors (of type CancelRecurringPaymentResult)</returns>
		public CancelRecurringPaymentResult CancelRecurringPayment(CancelRecurringPaymentRequest cancelPaymentRequest)
		{
			var result = new CancelRecurringPaymentResult();
			result.AddError("Canceling recurring payments not supported");
			return result;
		}

		/// <summary>
		/// 	<para>Gets a value indicating whether customers can complete a payment after order is placed but not completed (for redirection payment methods).</para>
		/// </summary>
		/// <param name="order">Order</param>
		/// <returns>false</returns>
		public bool CanRePostProcessPayment(Order order)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			//LibraPay payment gateway is a redirection payment method
			//It also validates whether order is also paid (after redirection) so customers will not be able to pay twice

			//payment status should be Pending
			if (order.PaymentStatus != PaymentStatus.Pending)
				return false;

			//let's ensure that at least 1 minute passed after order is placed
			if ((DateTime.UtcNow - order.CreatedOnUtc).TotalMinutes < 1)
				return false;

			return true;
		}

		/// <summary>
		/// 	<para>Gets a route for provider configuration: the admin page where config settings for the plugin are collected and saved.</para>
		/// </summary>
		/// <param name="actionName">Action name</param>
		/// <param name="controllerName">Controller name</param>
		/// <param name="routeValues">Route values</param>
		public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues)
		{
			actionName = "Configure";
			controllerName = "PaymentLibraPay";
			routeValues = new RouteValueDictionary() { { "Namespaces", "Nop.Plugin.Payments.LibraPay.Controllers" }, { "area", null } };
		}

		/// <summary>
		/// 	<para>Gets a route for payment info: the notification page shown to customer before being redirected to payment gateway.</para>
		/// </summary>
		/// <param name="actionName">Action name</param>
		/// <param name="controllerName">Controller name</param>
		/// <param name="routeValues">Route values</param>
		public void GetPaymentInfoRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues)
		{
			actionName = "PaymentInfo";
			controllerName = "PaymentLibraPay";
			routeValues = new RouteValueDictionary() { { "Namespaces", "Nop.Plugin.Payments.LibraPay.Controllers" }, { "area", null } };
		}

		public Type GetControllerType()
		{
			return typeof(PaymentLibraPayController);
		}

		/// <summary>
		/// Returns a value indicating whether payment method should be hidden during checkout
		/// </summary>
		/// <param name="cart">Shoping cart</param>
		/// <returns>true - hide; false - display.</returns>
		public bool HidePaymentMethod(IList<ShoppingCartItem> cart)
		{
			//you can put any logic here
			//for example, hide this payment method if all products in the cart are downloadable or hide this payment method if current customer is from certain country
			return false;
		}

		/// <summary>
		///		<para>This method is called when the plugin is installed in the nopCommerce shop.</para>
		///		<para>Saves settings and some localizable strings into the corresponding resources database table.</para>
		/// </summary>
		public override void Install()
		{
			//settings
			var settings = new LibraPayPaymentSettings()
			{
				UseSandbox = true,
				NotifyBeforeRedirect = true,
				MerchantName = "[ Company name, as set by LibraPay ]",
				MerchantURL = "www.YourSite-AsSetByLibraPay.ro",
				MerchantEmail = "Your.Address@YourSite.ro",
				EncryptionKey = "123456789012345",
				TerminalId = "12345678",
				AdditionalFee = 0
			};
			_settingService.SaveSetting(settings);

			#region locale resources (strings, images or others) to be used in UI
			// First add generic English resources/strings for all languages that are set up in the nopCommerce instance at the time of the plugin installation.
			// That is done to avoid having any language with missing resources; that would ruin the UI for that language (fields and texts would appear with resource names instead of natural language).
			XmlDocument xmlDocument = new XmlDocument();
			string resourceName;
			string resourceValue;
			IDictionary<string, string> resourcesDictionary = new Dictionary<string, string>();
			IList<Language> allInstalledLanguages = this._languageService.GetAllLanguages(true);
			Language aLanguage = null;
			#region get (or insert) the Romanian language in the system
			Language romanianLanguage = null;
			foreach (Language language in allInstalledLanguages)
			{
				if (language.LanguageCulture.ToLower().StartsWith("ro-ro") || language.UniqueSeoCode.ToLower() == "ro")
				{
					romanianLanguage = language;
				}
			}
			if (romanianLanguage == null)
			{
				romanianLanguage = new Language();
				romanianLanguage.Name = "Română";
				romanianLanguage.LanguageCulture = "ro-RO";
				romanianLanguage.FlagImageFileName = "ro.png";
				romanianLanguage.UniqueSeoCode = "ro";
				romanianLanguage.Rtl = false;
				romanianLanguage.Published = false;
				this._languageService.InsertLanguage(romanianLanguage);
				this._logger.Information("LibraPay plugin install: the Romanian language was missing, so it was installed, but it has to be published.");
				allInstalledLanguages = this._languageService.GetAllLanguages(true);
			}
			#endregion
			#region add the generic resources in English to be used by all installed languages
			FileInfo genericResourcesFile = new FileInfo(this._httpContext.Server.MapPath("~/Plugins/Payments.LibraPay/LocalizationStrings.xml"));
			if (!genericResourcesFile.Exists) genericResourcesFile = new FileInfo(this._httpContext.Server.MapPath("~/Plugins/Payments.LibraPay/LocalizationStrings.en.xml"));
			if (genericResourcesFile.Exists)
			{
				xmlDocument.Load(genericResourcesFile.FullName);
				this._logger.Information(string.Format("LibraPay plugin install: loading and adding generic localizable resources from file '{0}'.", genericResourcesFile.FullName));
				foreach (XmlNode xmlNode in xmlDocument.SelectNodes("//LocaleResource"))
				{
					resourceName = xmlNode.Attributes["Name"].Value;
					resourceValue = xmlNode.ChildNodes[0].InnerText;
					resourcesDictionary.Add(resourceName, resourceValue);
				}
				foreach (Language language in allInstalledLanguages)
				{
					InstallResources(language, resourcesDictionary);
				}
				resourcesDictionary.Clear();
			}
			#endregion
			#region install translated string resources from each of the localization files present within the plugin directory
			DirectoryInfo pluginDirectory = new DirectoryInfo(this._httpContext.Server.MapPath("~/Plugins/Payments.LibraPay/"));
			FileInfo[] localizableResourceFiles = pluginDirectory.GetFiles("LocalizationStrings.*.xml");
			string languageAbbreviationFromFileName;
			foreach (FileInfo resourcesFile in localizableResourceFiles)
			{
				aLanguage = null;
				languageAbbreviationFromFileName = resourcesFile.Name.Substring(20, 2).ToLower();
				foreach (Language language in allInstalledLanguages)
				{
					if (language.UniqueSeoCode.ToLower() == languageAbbreviationFromFileName) aLanguage = language;
					if (aLanguage == null || language.LanguageCulture.ToLower().StartsWith(languageAbbreviationFromFileName)) aLanguage = language;
				}
				if (aLanguage == null) continue;
				xmlDocument.Load(resourcesFile.FullName);
				this._logger.Information(string.Format("LibraPay plugin install: loading and adding translated resource strings from file '{0}'.", resourcesFile.Name));
				foreach (XmlNode xmlNode in xmlDocument.SelectNodes("//LocaleResource"))
				{
					resourceName = xmlNode.Attributes["Name"].Value;
					resourceValue = xmlNode.ChildNodes[0].InnerText;
					resourcesDictionary.Add(resourceName, resourceValue);
				}
				InstallResources(aLanguage, resourcesDictionary);
				resourcesDictionary.Clear();
			}
			#endregion
			#endregion

			base.Install();
		}

		/// <summary>
		///		<para>This method is called when the plugin is installed in the nopCommerce shop.</para>
		///		<para>Deletes some localizable strings into the corresponding database table.</para>
		/// </summary>
		public override void Uninstall()
		{
			//settings
			_settingService.DeleteSetting<LibraPayPaymentSettings>();

			//locales
			XmlDocument xmlDocument = new XmlDocument();
			FileInfo genericResourcesFile = new FileInfo(this._httpContext.Server.MapPath("~/Plugins/Payments.LibraPay/LocalizationStrings.xml"));
			if (!genericResourcesFile.Exists) genericResourcesFile = new FileInfo(this._httpContext.Server.MapPath("~/Plugins/Payments.LibraPay/LocalizationStrings.en.xml"));
			if (genericResourcesFile.Exists)
			{
				xmlDocument.Load(genericResourcesFile.FullName);
				this._logger.Information(string.Format("LibraPay plugin uninstall: deleting localized resources found according to file '{0}'.", genericResourcesFile.FullName));
				foreach (XmlNode xmlNode in xmlDocument.SelectNodes("//LocaleResource"))
				{
					this.DeletePluginLocaleResource(xmlNode.Attributes["Name"].Value);
				}
			}

			base.Uninstall();
		}

		#endregion

		#region Properties

		/// <summary>
		/// 	<para>Gets a value indicating whether capture is supported. It is.</para>
		/// 	<para>Capture means getting the money being transfered into the merchant's account after the order has been delivered.</para>
		/// </summary>
		public bool SupportCapture
		{
			get
			{
				return false;
			}
		}

		/// <summary>
		/// 	<para>Gets a value indicating whether partial refund is supported.</para>
		/// 	<para>Currently refund is not supported by LibraPay plugin. The merchant will refund money using the LibraPay payment gateway.</para>
		/// 	<para>The refunded orders will need to be marked accordingly in nopCommerce by the merchant.</para>
		/// </summary>
		public bool SupportPartiallyRefund
		{
			get
			{
				return false;
			}
		}

		/// <summary>
		/// 	<para>Gets a value indicating whether refund is supported.</para>
		/// 	<para>Currently refund is not supported by LibraPay plugin. The merchant will refund money using the LibraPay payment gateway.</para>
		/// 	<para>The refunded orders will need to be marked accordingly in nopCommerce by the merchant.</para>
		/// </summary>
		public bool SupportRefund
		{
			get
			{
				return false;
			}
		}

		/// <summary>
		/// 	<para>Gets a value indicating whether void payment is supported.</para>
		/// </summary>
		public bool SupportVoid
		{
			get
			{
				return false;
			}
		}

		/// <summary>
		/// 	<para>Says how a recurring payment will be handled (not supported by this plugin).</para>
		/// </summary>
		public RecurringPaymentType RecurringPaymentType
		{
			get
			{
				return RecurringPaymentType.NotSupported;
			}
		}

		/// <summary>
		/// 	<para>Gets the payment method type: a redirect to a credit card payment gateway.</para>
		/// 	<para>No credit card / holder data is collected or processed by nopCommerce LibraPay plugin.</para>
		/// </summary>
		public PaymentMethodType PaymentMethodType
		{
			get
			{
				return PaymentMethodType.Redirection;
			}
		}

		/// <summary>
		///		<para>Gets a value indicating whether we should display a payment information page for this plugin</para>
		/// </summary>
		public bool SkipPaymentInfo
		{
			get
			{
				return true;
			}
		}

		#endregion
	}
}
